StretchType = Enum{
    "None",
    "Horizontal",
    "Vertical",
    "Both",
}

local function checkAddTag(self, tag)
    if self:hasTag(tag) then
        Console.error("already has tag ", tag)
        return
    end
    self:addTag(tag)
end
local function updateStretchSizeTags(self, stretchType)
    if self._stretchType == StretchType.Both then
        self:removeTags("WidthController", "HeightController")
    elseif self._stretchType == StretchType.Horizontal then
        self:removeTag("WidthController")
    elseif stretchType == StretchType.Vertical then
        self:removeTag("HeightController")
    end

    if stretchType == StretchType.Both then
        checkAddTag(self, "WidthController")
        checkAddTag(self, "HeightController")
    elseif stretchType == StretchType.Horizontal then
        checkAddTag(self, "WidthController")
    elseif stretchType == StretchType.Vertical then
        checkAddTag(self, "HeightController")
    end
end

Stretch = {
    name = "stretch",
    props = {
        _stretchType = StretchType.None,
        _stretchOffsetX = 0,
        _stretchPercentX = 1,
        _stretchOffsetY = 0,
        _stretchPercentY = 1,
        _fillLayout = false,
        stretchDirty = true
    },
    gets = {
        fillLayout = function(self)
            return self._fillLayout
        end,
        stretchType = function(self)
            return self._stretchType
        end,
        stretchOffsetX = function(self)
            return self._stretchOffsetX
        end,
        stretchOffsetY = function(self)
            return self._stretchOffsetY
        end,
        stretchPercentX = function(self)
            return self._stretchPercentX
        end,
        stretchPercentY = function(self)
            return self._stretchPercentY
        end
    },
    sets = {
        stretchType = function(self, key, val)
            if val == self._stretchType then
                return
            end
            updateStretchSizeTags(self, val)
            self._stretchType = val
            self:setStretchDirty()
        end,
        stretchOffsetX = function(self, key, val)
            self._stretchOffsetX = val
            self:setStretchDirty()
        end,
        stretchOffsetY = function(self, key, val)
            self._stretchOffsetY = val
            self:setStretchDirty()
        end,
        stretchPercentX = function(self, key, val)
            self._stretchPercentX = val
            self:setStretchDirty()
        end,
        stretchPercentY = function(self, key, val)
            self._stretchPercentY = val
            self:setStretchDirty()
        end,
        fillLayout = function(self, key, val)
            self._fillLayout = val
            self:setStretchDirty()
        end
    }
}

function Stretch:updateStretch()
    if self._stretchType == StretchType.Both then
        self:updateStretchWidth()
        self:updateStretchHeight()
    elseif self._stretchType == StretchType.Horizontal then
        self:updateStretchWidth()
    elseif self._stretchType == StretchType.Vertical then
        self:updateStretchHeight()
    end
    self.stretchDirty = false
end

function Stretch:updateStretchWidth()
    if not self.parent then
        return
    end
    local width = self.parent.width
    if self.fillLayout and (self.parent.layoutSizeMode == LayoutSizeMode.Custom or self.parent.layoutSizeMode == LayoutSizeMode.StretchY) then
        if self.parent.layoutType == LayoutType.Horizontal then
            local othersWidth = 0
            for i, v in ipairs(self.parent.children) do
                if v ~= self and v.active then
                    othersWidth = othersWidth + v.width
                end
            end
            width = self.parent.width - self.parent.leftPadding - self.parent.rightPadding - othersWidth
        elseif self.parent.layoutType == LayoutType.Vertical then
            width = self.parent.width - self.parent.leftPadding - self.parent.rightPadding
        end
    end
    self.width = width * self._stretchPercentX + self._stretchOffsetX
end

function Stretch:updateStretchHeight()
    if not self.parent then
        return
    end
    local height = self.parent.height
    if self.fillLayout and (self.parent.layoutSizeMode == LayoutSizeMode.Custom or self.parent.layoutSizeMode == LayoutSizeMode.StretchX) then
        if self.parent.layoutType == LayoutType.Horizontal then
            height = self.parent.height - self.parent.topPadding - self.parent.bottomPadding
        elseif self.parent.layoutType == LayoutType.Vertical then
            local othersHeight = 0
            for i, v in ipairs(self.parent.children) do
                if v ~= self and v.active then
                    othersHeight = othersHeight + v.height
                end
            end
            height = self.parent.height - self.parent.topPadding - self.parent.bottomPadding - othersHeight
        end
    end
    self.height = height * self._stretchPercentY + self._stretchOffsetY
end
local function onSetParent(self, parent, old)
    if old then
        old:off(NodeEvent.WidthDirty, self.setStretchDirty, self)
        old:off(NodeEvent.HeightDirty, self.setStretchDirty, self)
    end
    if parent then
        if self.stretchType == StretchType.Both then
            parent:on(NodeEvent.WidthDirty, self.setStretchDirty, self)
            parent:on(NodeEvent.HeightDirty, self.setStretchDirty, self)
        elseif self.stretchType == StretchType.Horizontal then
            parent:on(NodeEvent.WidthDirty, self.setStretchDirty, self)
        elseif self.stretchType == StretchType.Vertical then
            parent:on(NodeEvent.HeightDirty, self.setStretchDirty, self)
        end
        self:setStretchDirty()
    end
end

function Stretch:ctor(args)
    args = args or {}
    self.stretchType = args.stretchType or StretchType.Both
    self:on(NodeEvent.SetParent, onSetParent, self)
    if self.parent then
        onSetParent(self, self.parent)
    end
end


function Stretch:setStretchDirty()
    self.stretchDirty = true
    if self.stretchType == StretchType.Both then
        self:emit(NodeEvent.WidthDirty)
        self:emit(NodeEvent.HeightDirty)
    elseif self.stretchType == StretchType.Horizontal then
        self:emit(NodeEvent.WidthDirty)
    elseif self.stretchType == StretchType.Vertical then
        self:emit(NodeEvent.HeightDirty)
    end
end